// Copyright 2015 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

#include <stddef.h>

#include "base/trace_event/trace_event.h"
#include "base/trace_event/trace_event_impl.h"
#include "base/trace_event/trace_log.h"
#include "base/trace_event/trace_sampling_thread.h"

namespace base {
namespace trace_event {

    class TraceBucketData {
    public:
        TraceBucketData(base::subtle::AtomicWord* bucket,
            const char* name,
            TraceSampleCallback callback);
        ~TraceBucketData();

        TRACE_EVENT_API_ATOMIC_WORD* bucket;
        const char* bucket_name;
        TraceSampleCallback callback;
    };

    TraceSamplingThread::TraceSamplingThread()
        : thread_running_(false)
        , waitable_event_for_testing_(WaitableEvent::ResetPolicy::AUTOMATIC,
              WaitableEvent::InitialState::NOT_SIGNALED)
    {
    }

    TraceSamplingThread::~TraceSamplingThread() { }

    void TraceSamplingThread::ThreadMain()
    {
        PlatformThread::SetName("Sampling Thread");
        thread_running_ = true;
        const int kSamplingFrequencyMicroseconds = 1000;
        while (!cancellation_flag_.IsSet()) {
            PlatformThread::Sleep(
                TimeDelta::FromMicroseconds(kSamplingFrequencyMicroseconds));
            GetSamples();
            waitable_event_for_testing_.Signal();
        }
    }

    // static
    void TraceSamplingThread::DefaultSamplingCallback(
        TraceBucketData* bucket_data)
    {
        TRACE_EVENT_API_ATOMIC_WORD category_and_name = TRACE_EVENT_API_ATOMIC_LOAD(*bucket_data->bucket);
        if (!category_and_name)
            return;
        const char* const combined = reinterpret_cast<const char* const>(category_and_name);
        const char* category_group;
        const char* name;
        ExtractCategoryAndName(combined, &category_group, &name);
        TRACE_EVENT_API_ADD_TRACE_EVENT(
            TRACE_EVENT_PHASE_SAMPLE,
            TraceLog::GetCategoryGroupEnabled(category_group), name,
            trace_event_internal::kGlobalScope, trace_event_internal::kNoId, 0,
            NULL, NULL, NULL, NULL, 0);
    }

    void TraceSamplingThread::GetSamples()
    {
        for (size_t i = 0; i < sample_buckets_.size(); ++i) {
            TraceBucketData* bucket_data = &sample_buckets_[i];
            bucket_data->callback.Run(bucket_data);
        }
    }

    void TraceSamplingThread::RegisterSampleBucket(
        TRACE_EVENT_API_ATOMIC_WORD* bucket,
        const char* const name,
        TraceSampleCallback callback)
    {
        // Access to sample_buckets_ doesn't cause races with the sampling thread
        // that uses the sample_buckets_, because it is guaranteed that
        // RegisterSampleBucket is called before the sampling thread is created.
        DCHECK(!thread_running_);
        sample_buckets_.push_back(TraceBucketData(bucket, name, callback));
    }

    // static
    void TraceSamplingThread::ExtractCategoryAndName(const char* combined,
        const char** category,
        const char** name)
    {
        *category = combined;
        *name = &combined[strlen(combined) + 1];
    }

    void TraceSamplingThread::Stop()
    {
        cancellation_flag_.Set();
    }

    void TraceSamplingThread::WaitSamplingEventForTesting()
    {
        waitable_event_for_testing_.Wait();
    }

    TraceBucketData::TraceBucketData(base::subtle::AtomicWord* bucket,
        const char* name,
        TraceSampleCallback callback)
        : bucket(bucket)
        , bucket_name(name)
        , callback(callback)
    {
    }

    TraceBucketData::~TraceBucketData() { }

} // namespace trace_event
} // namespace base
